PHP基础安全开发

环境搭建

phpstorm

PHPStorm 是由 JetBrains 开发的一款集成开发环境 (IDE),专门为 PHP 开发设计。以下是 PHPStorm 的一些关键特点和优势:

  1. 智能编码辅助:PHPStorm 提供代码补全、错误检测、重构和导航功能,可以显著提升开发效率。
  2. 调试和测试:内置强大的调试工具,支持 Xdebug 和 Zend Debugger,方便开发者进行代码调试。还集成了 PHPUnit 进行单元测试。
  3. 代码质量工具:集成了代码质量检查工具,如 PHP_CodeSniffer、PHP Mess Detector 和 PHP CS Fixer,有助于保持代码整洁和符合规范。
  4. 支持多种框架:支持主流 PHP 框架,如 Laravel、Symfony、Zend Framework、Yii 等,提供相应的框架辅助功能。
  5. 前端开发支持:内置 HTML、CSS 和 JavaScript 的支持,包括 Sass、Less、TypeScript 和 CoffeeScript,适合全栈开发。
  6. 数据库和 SQL 支持:提供强大的数据库工具,可以直接在 IDE 中管理和查询数据库。
  7. 版本控制集成:支持 Git、SVN、Mercurial 等版本控制系统,方便代码管理和协作。
  8. 插件扩展:支持安装各种插件,进一步扩展其功能,满足个性化需求。

下载链接
https://www.jetbrains.com/zh-cn/phpstorm/download/#section=windows

配置本地PHP解释器

PHPStorm 本身并不包含 PHP 解释器,它只是一个 IDE,提供开发和调试功能。即使你已经下载并安装了 PHPStorm,它仍然需要你配置 PHP 解释器,因为 PHPStorm 需要知道哪个 PHP 解释器用来运行和调试 PHP 代码。
官方文档

我选择下载XAMPP(XAMPP 是一个免费开源的跨平台 Web 服务器解决方案,主要用于开发和测试 Web 应用程序。)
//和PHPstudy功能类似
XAMPP下载链接

手搓评论系统

首先先让chatGPT生成一个评论系统的表单

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>评论页面</title>
    <style>
        body {
            font-family: Arial, sans-serif;
        }
        .comment-form {
            max-width: 500px;
            margin: 0 auto;
            padding: 1em;
            background: #f9f9f9;
            border-radius: 5px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
        }
        .comment-form h2 {
            margin-bottom: 1em;
        }
        .comment-form label {
            display: block;
            margin-bottom: 0.5em;
        }
        .comment-form input,
        .comment-form textarea {
            width: 100%;
            padding: 0.5em;
            margin-bottom: 1em;
            border: 1px solid #ccc;
            border-radius: 5px;
        }
        .comment-form button {
            padding: 0.5em 2em;
            background: #007BFF;
            border: none;
            color: white;
            border-radius: 5px;
            cursor: pointer;
        }
        .comment-form button:hover {
            background: #0056b3;
        }
    </style>
</head>
<body>
    <div class="comment-form">
        <h2>发表评论</h2>
        <form action="" method="post">//注意此处的action,这里指的是将数据发送到哪一个文件,留空指的是发送给自身文件
            <label for="username">用户名:</label>
            <input type="text" id="username" name="username" required>

            <label for="content">评论内容:</label>
            <textarea id="content" name="content" rows="5" required></textarea>

            <button type="submit">提交评论</button>
        </form>
    </div>
</body>
</html>

Pasted image 20240723211858.png

数据库连接 mysqli_connect

$dbip='localhost';  
$dbuser='root';  
$dbpass='root';  
$dbname='kongyu';  
$con=mysqli_connect($dbip,$dbuser,$dbpass,$dbname);  
  
// 检查连接  
if (!$con)  
{  
    die("连接错误: " . mysqli_connect_error());  
}  
else  
{  
    echo "ok";  
}

其中关于mysqli的函数使用见:
https://www.runoob.com/php/php-ref-mysqli.html

常用:

超全局变量

$GLOBALS:这种全局变量用于在 PHP 脚本中的任意位置访问全局变量

$_SERVER:这种超全局变量保存关于报头、路径和脚本位置的信息。

$_REQUEST:$_REQUEST 用于收集 HTML 表单提交的数据。

$_POST:广泛用于收集提交method="post" 的HTML表单后的表单数据。

$_GET:收集URL中的发送的数据。也可用于收集提交HTML表单数据(method="get")

$_FILES:文件上传且处理包含通过HTTP POST方法上传给当前脚本的文件内容。

$_ENV:是一个包含服务器端环境变量的数组。

$_COOKIE:是一个关联数组,包含通过cookie传递给当前脚本的内容。

$_SESSION:是一个关联数组,包含当前脚本中的所有session内容。

参考文章:
PHP 全局变量 - 超全局变量
超全局变量

执行简单数据库操作-插入数据

首先数据库操作语句简单整理如下:

查:select * from 表名 where 列名='条件';

增:insert into 表名(`列名1`, `列名2`) value('列1值1', '列2值2');

删:delete from 表名 where 列名 = '条件';

改:update 表名 set 列名 = 数据 where 列名 = '条件';
if (isset($username)) {  
    $sql = "insert into comment(`username`, `content`,`ip`,`uagent`) value('$username','$content','$ip','$uagent');";  
    // 执行SQL查询  
    if (mysqli_query($con, $sql)) {  
        echo "<script>alert('留言成功!')</script>";  
    } else {  
        echo "错误: " . mysqli_error($con);  
    }  
}

这里就是先构造了个字符串sqlmysqliquery(con, $sql)`会执行数据库操作

其中isset($username)确保username中有值才会执行

简单的删除评论

<?php  
include '../config.php';  
  
$delstr=@$_GET['del'];  
if(isset($delstr)){  
    $sql2="delete from comment where username = '$delstr';";  
    if(mysqli_query($con,$sql2)){  
        echo "<script>alert('删除成功!')</script>";  
    }  
}  
  
$sql1="select * from comment";  
$data=mysqli_query($con,$sql1);  
while ($row=mysqli_fetch_row($data)) {  
    echo '<hr>';  
    echo '用户名:'.$row[0].'<br>';  
    echo '内容:'.$row[1].'<br>';  
    echo 'IP地址:'.$row[2].'<br>';  
    echo 'UA浏览器:'.$row[3].'<br>';  
    echo "<a href='comment-admin.php?del=$row[0]'>删除</a>";  
}

这部分关键代码也就是
echo "<a href='comment-admin.php?del=$row[0]'>删除</a>";
这个会输出一个按钮,点击就会访问指定的路径,也就是comment-admin.php?del=$row[0]
Pasted image 20240723223339.png
下面利用全局变量接收del来进行删除

这样的逻辑漏洞就显而易见,也就是如果直接构造一个url也可以实现删除操作

http://localhost:63342/kongyu1/admin/comment-admin.php?del=555

进行简单的函数封装

<?php  
include "config.php";  
  
  
//添加评论  
function add_comment($con){  
    $username=@$_POST['username'];  
    $content=@$_POST['content'];  
    $ip = @$_SERVER['REMOTE_ADDR'];  
    $uagent = @$_SERVER['HTTP_USER_AGENT'];  
    if (isset($username)) {  
        $sql = "insert into comment(`username`, `content`,`ip`,`uagent`) value('$username','$content','$ip','$uagent');";  
        // 执行SQL查询  
        if (mysqli_query($con, $sql)) {  
            echo "<script>alert('留言成功!')</script>";  
        } else {  
            echo "错误: " . mysqli_error($con);  
        }  
    }  
}  
  
  
//显示评论  
function show_comment($con,$del){  
    $sql1="select * from comment";  
    $data=mysqli_query($con,$sql1);  
    while ($row=mysqli_fetch_row($data)) {  
        echo '<hr>';  
        echo '用户名:'.$row[0].'<br>';  
        echo '内容:'.$row[1].'<br>';  
        echo 'IP地址:'.$row[2].'<br>';  
        echo 'UA浏览器:'.$row[3].'<br>';  
        if($del=='del'){  
            echo "<a href='gbook-admin.php?del=$row[0]'>删除</a>";  
        }  
    }  
}  
  
if (!$con) {  
    die("连接错误: " . mysqli_connect_error());  
} else {  
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {  
        add_comment($con);  
    }  
    show_comment($con, 'x');  
}

进一步优化

<?php
include "config.php";

// 添加评论
function add_comment($con) {
    $username = $_POST['username'] ?? null;
    $content = $_POST['content'] ?? null;
    $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
    $uagent = $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown';

    if ($username && $content) {
        $stmt = $con->prepare("INSERT INTO comment (`username`, `content`, `ip`, `uagent`) VALUES (?, ?, ?, ?)");
        $stmt->bind_param('ssss', $username, $content, $ip, $uagent);

        if ($stmt->execute()) {
            echo "<script>alert('留言成功!')</script>";
        } else {
            echo "错误: " . $con->error;
        }

        $stmt->close();
    } else {
        echo "<script>alert('用户名和内容不能为空!')</script>";
    }
}

// 显示评论
function show_comment($con, $del) {
    $sql = "SELECT * FROM comment";
    $result = $con->query($sql);

    if ($result->num_rows > 0) {
        while ($row = $result->fetch_assoc()) {
            echo '<hr>';
            echo '用户名:' . htmlspecialchars($row['username'], ENT_QUOTES, 'UTF-8') . '<br>';
            echo '内容:' . nl2br(htmlspecialchars($row['content'], ENT_QUOTES, 'UTF-8')) . '<br>';
            echo 'IP地址:' . htmlspecialchars($row['ip'], ENT_QUOTES, 'UTF-8') . '<br>';
            echo 'UA浏览器:' . htmlspecialchars($row['uagent'], ENT_QUOTES, 'UTF-8') . '<br>';

            if ($del === 'del') {
                echo "<a href='gbook-admin.php?del=" . urlencode($row['username']) . "'>删除</a>";
            }
        }
    } else {
        echo "没有评论";
    }
}

if (!$con) {
    die("连接错误: " . mysqli_connect_error());
} else {
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        add_comment($con);
    }
    show_comment($con, 'x');
}
?>

sql预处理语句

预处理语句(Prepared Statements)是数据库查询的一种方法,通过将查询模板和数据分离来提高安全性和性能。它们是防止SQL注入攻击的重要工具。

如何工作

  1. 准备查询模板:首先,数据库服务器将包含参数占位符的SQL查询模板发送到数据库。例如:

    INSERT INTO comment (`username`, `content`, `ip`, `uagent`) VALUES (?, ?, ?, ?)
    
  2. 编译查询:数据库服务器预编译这个查询模板并准备好执行。

  3. 绑定参数:应用程序将实际的值绑定到这些参数占位符。比如,在PHP中使用 bind_param 方法:

    $stmt = $con->prepare("INSERT INTO comment (`username`, `content`, `ip`, `uagent`) VALUES (?, ?, ?, ?)");
    $stmt->bind_param('ssss', $username, $content, $ip, $uagent);
    
  4. 执行查询:数据库服务器使用绑定的参数执行预编译的查询。

防止SQL注入攻击

SQL注入攻击通常发生在用户输入被直接拼接到SQL查询字符串中时,例如:

$username = $_POST['username'];
$sql = "SELECT * FROM users WHERE username = '$username'";

攻击者可以通过在输入中插入恶意SQL代码来改变查询的行为,例如:

' OR '1'='1

结果查询变成:

SELECT * FROM users WHERE username = '' OR '1'='1'

这将返回所有用户数据,因为 OR '1'='1' 永远为真。

通过使用预处理语句,查询和数据被分离,用户输入被视为纯粹的数据,不会被解析为SQL代码:

$username = $_POST['username'];
$stmt = $con->prepare("SELECT * FROM users WHERE username = ?");
$stmt->bind_param('s', $username);

这里,$username 的内容永远不会直接插入到SQL查询字符串中,因此即使包含恶意代码也无济于事。

优点

  1. 安全性:有效防止SQL注入攻击。
  2. 性能:对于重复执行的相同查询模板,数据库服务器只需编译一次,提高了执行效率。
  3. 代码清晰:查询模板和数据分离,代码更清晰易读。

使用第三方插件ueditor

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <meta name="viewport" content="width=device-width, initial-scale=1.0">  
    <title>评论页面</title>  
    <style>        body {  
            font-family: Arial, sans-serif;  
        }  
        .comment-form {  
            max-width: 500px;  
            margin: 0 auto;  
            padding: 1em;  
            background: #f9f9f9;  
            border-radius: 5px;  
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);  
        }  
        .comment-form h2 {  
            margin-bottom: 1em;  
        }  
        .comment-form label {  
            display: block;  
            margin-bottom: 0.5em;  
        }  
        .comment-form input,  
        .comment-form textarea {  
            width: 100%;  
            padding: 0.5em;  
            margin-bottom: 1em;  
            border: 1px solid #ccc;  
            border-radius: 5px;  
        }  
        .comment-form button {  
            padding: 0.5em 2em;  
            background: #007BFF;  
            border: none;  
            color: white;  
            border-radius: 5px;  
            cursor: pointer;  
        }  
        .comment-form button:hover {  
            background: #0056b3;  
        }  
    </style>  
    <!-- 引入UEditor配置文件 -->  
    <script type="text/javascript" src="/ueditor/ueditor.config.js"></script>  
    <!-- 引入UEditor核心文件 -->  
    <script type="text/javascript" src="/ueditor/ueditor.all.min.js"></script>  
</head>  
<body>  
<div class="comment-form">  
    <h2>发表评论</h2>  
    <form action="" method="post">  
        <label for="username">用户名:</label>  
        <input type="text" id="username" name="username" required>  
  
        <label for="content">评论内容:</label>  
        <!-- 将textarea替换为UEditor -->  
        <script id="content" name="content" type="text/plain" style="width:100%;height:300px;"></script>  
  
        <button type="submit">提交评论</button>  
    </form></div>  
  
<!-- 初始化UEditor -->  
<script type="text/javascript">  
    var ue = UE.getEditor('content');  
</script>  
</body>  
</html>